﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Xenta.Api;
using Xenta.Structs;
using Xenta.Web;

namespace Xenta.Utils
{
    /// <summary>
    /// Contains the API extension methods.
    /// </summary>
    public static class Api
    {
        #region Constants

        private static readonly JsonSerializerSettings JSON_SETTINGS = new JsonSerializerSettings
        {
            Formatting = Formatting.None,
            NullValueHandling = NullValueHandling.Ignore,
            Culture = CultureInfo.InvariantCulture
        };
        private static readonly Guid OPERATION_SVC = new Guid("4ee2accb-d305-4419-92de-f4420ee15d4f");
        private static readonly Guid DOWNLOAD_SVC = new Guid("b5bf5665-da4f-4762-9648-05ef294b3fb6");
        private static readonly Guid UPLOAD_SVC = new Guid("b737dbbb-6232-4954-8b18-83909c3bf0c9");
        private static readonly Guid REPORT_SVC = new Guid("81a3f1f3-a74e-4f94-a082-5a960e29e952");
        private static readonly Guid IMPORT_SVC = new Guid("16522955-e49d-4c88-b291-db0920cbdfb4");
        private static readonly Guid EXPORT_SVC = new Guid("63fd2334-2682-4c83-b939-1b3dc2a18b76");

        #endregion

        #region Methods
        
        /// <summary>
        /// Process an operation.
        /// </summary>
        /// <param name="svc">The service instance.</param>
        /// <param name="name">The name of operation.</param>
        /// <param name="properties">The operation properties.</param>
        /// <param name="handling">The additional handler properties.</param>
        /// <param name="tform">The additional transformations.</param>
        /// <returns>The operation result as a dynamic object.</returns>
        public static dynamic ProcessOperation(this Service svc, 
            string name, 
            dynamic properties = null, 
            dynamic handling = null,
            IEnumerable<TransformInfo> tform = null)
        {
            if(String.IsNullOrWhiteSpace(name))
                throw new ArgumentException("name");

            dynamic result = null;
            var hndInfo = new HandlerInfo
            {
                Guid = OPERATION_SVC,
                Properties = new Dictionary<String, String>
                {
                    {"OperationName", name},
                    {"CID", svc.CID.ToString()},
                    {"SID", svc.SID.ToString()}
                }
            };
            if(handling != null)
                hndInfo.Properties.AddValues((object)handling);

            using(var api = new ApiClient())
            {
                var req = new ApiRequest
                {
                    Infrastructure = svc.Context.Infrastructure,
                    Handlers = new List<HandlerInfo>
                    {
                        hndInfo
                    },
                    Data = properties != null 
                        ? new MemoryStream() 
                        : Stream.Null,
                    MimeType = "application/json"
                };
                if(tform != null)
                    req.Handlers.AddRange(tform.Select(x => x.ToHandlerInfo()));

                if(properties != null)
                {
                    using(var streamWriter = new StreamWriter(req.Data, 
                        Encoding.Unicode, 
                        1024, 
                        true))
                    {
                        using(var jsonWriter = new JsonTextWriter(streamWriter)
                        {
                            CloseOutput = false
                        })
                        {
                            JsonSerializer.Create(JSON_SETTINGS)
                                .Serialize(jsonWriter, properties);
                        }
                    }
                    req.Data.Seek(0, SeekOrigin.Begin);
                }

                var rsp = api.Process(req);
                using(var streamReader = new StreamReader(rsp.Data, 
                    Encoding.Unicode, 
                    true, 
                    1024, 
                    true))
                {
                    using(var jsonReader = new JsonTextReader(streamReader)
                    {
                        CloseInput = false
                    })
                    {
                        dynamic tmp = JsonSerializer
                            .Create(JSON_SETTINGS)
                            .Deserialize(jsonReader);
                        if(tmp != null)
                            result = tmp.Result;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Downloads a file.
        /// </summary>
        /// <param name="svc">The service instance.</param>
        /// <param name="guid">The file global unique identifier.</param>
        /// <param name="mimeType">When this method returns, contains the MIME type</param>
        /// <param name="tform">The additional transformations.</param>
        /// <returns>The file stream.</returns>
        public static Stream DownloadFile(this Service svc, 
            Guid guid,
            out string mimeType,
            IEnumerable<TransformInfo> tform = null)
        {
            if(guid == Guid.Empty)
                throw new ArgumentException("guid");

            using(var api = new ApiClient())
            {
                var req = new ApiRequest
                {
                    Infrastructure = svc.Context.Infrastructure,
                    Handlers = new List<HandlerInfo>
                    {
                        new HandlerInfo
                        {
                            Guid = DOWNLOAD_SVC,
                            Properties = new Dictionary<String, String>
                            {
                                {"FileGuid", guid.ToString()},
                                {"CID", svc.CID.ToString()},
                                {"SID", svc.SID.ToString()}
                            }
                        } 
                    },
                    Data = Stream.Null,
                    MimeType = String.Empty
                };
                if(tform != null)
                    req.Handlers.AddRange(tform.Select(x => x.ToHandlerInfo()));

                var rsp = api.Process(req);
                mimeType = rsp.MimeType;
                return rsp.Data;
            }
        }

        /// <summary>
        /// Uploads a file.
        /// </summary>
        /// <param name="svc">The service instance.</param>
        /// <param name="guid">The file global unique identifier.</param>
        /// <param name="stream">The file stream.</param>
        /// <param name="tform">The additional transformations.</param>
        public static void UploadFile(this Service svc, 
            Guid guid,
            Stream stream,
            IEnumerable<TransformInfo> tform = null)
        {
            if(stream == null)
                throw new ArgumentNullException("stream");
            if(guid == Guid.Empty)
                throw new ArgumentException("guid");

            using(var api = new ApiClient())
            {
                var req = new ApiRequest
                {
                    Infrastructure = svc.Context.Infrastructure,
                    Handlers = new List<HandlerInfo>
                    {
                        new HandlerInfo
                        {
                            Guid = UPLOAD_SVC,
                            Properties = new Dictionary<String, String>
                            {
                                {"FileGuid", guid.ToString()},
                                {"CID", svc.CID.ToString()},
                                {"SID", svc.SID.ToString()}
                            }
                        }   
                    },
                    Data = stream,
                    MimeType = String.Empty
                };
                if(tform != null)
                    req.Handlers.AddRange(tform.Select(x => x.ToHandlerInfo()));
                api.Process(req);
            }
        }

        /// <summary>
        /// Builds a report.
        /// </summary>
        /// <param name="svc">The service instance.</param>
        /// <param name="name">The name of report operation.</param>
        /// <param name="properties">The report properties.</param>
        /// <param name="mimeType">When this method returns, contains the report MIME type.</param>
        /// <param name="elapsedTime">When this method returns, contains the elapsed time in miliseconds.</param>
        /// <param name="tform">The additional transformations.</param>
        /// <returns>An XElement which contains the report data.</returns>
        public static Stream BuildReport(this Service svc, 
            string name, 
            IDictionary<String, String> properties, 
            out string mimeType,
            out long elapsedTime,
            IEnumerable<TransformInfo> tform = null)
        {
            if(properties == null)
                throw new ArgumentNullException("properties");
            if(String.IsNullOrWhiteSpace(name))
                throw new ArgumentException("name");

            using(var api = new ApiClient())
            {
                var req = new ApiRequest
                {
                    Infrastructure = svc.Context.Infrastructure,
                    Handlers = new List<HandlerInfo>
                    {
                        new HandlerInfo
                        {
                            Guid = REPORT_SVC,
                            Properties = new Dictionary<String, String>(properties)
                            {
                                {"OperationName", name},
                                {"CID", svc.CID.ToString()},
                                {"SID", svc.SID.ToString()}
                            }
                        }
                    },
                    Data = Stream.Null,
                    MimeType = String.Empty
                };
                if(tform != null)
                    req.Handlers.AddRange(tform.Select(x => x.ToHandlerInfo()));
                var rsp = api.Process(req);
                mimeType = rsp.MimeType;
                elapsedTime = rsp.Handlers.Single(x => x.Guid == REPORT_SVC)
                    .Properties["Result"].ParseAs<Int64>();
                return rsp.Data;
            }
        }

        /// <summary>
        /// Imports data using the specified import 
        /// operation and format.
        /// </summary>
        /// <param name="svc">The service instance.</param>
        /// <param name="name">The name of operation.</param>
        /// <param name="properties">The operation properties.</param>
        /// <param name="formatGuid">The global unique identifier of the format.</param>
        /// <param name="stream">The data stream.</param>
        /// <param name="transformations">The additional transformations.</param>
        /// <returns>The numer of import results.</returns>
        public static int ImportData(this Service svc, 
            string name, 
            IDictionary<String, String> properties, 
            Guid formatGuid,
            Stream stream,
            IEnumerable<TransformInfo> transformations = null)
        {
            if(stream == null)
                throw new ArgumentNullException("stream");
            if(properties == null)
                throw new ArgumentNullException("properties");
            if(String.IsNullOrWhiteSpace(name))
                throw new ArgumentException("name");

            using(var api = new ApiClient())
            {
                var req = new ApiRequest
                {
                    Infrastructure = svc.Context.Infrastructure,
                    Handlers = new List<HandlerInfo>
                    {
                        new HandlerInfo
                        {
                            Guid = IMPORT_SVC,
                            Properties = new Dictionary<String, String>(properties)
                            {
                                {"OperationName", name},
                                {"FormatGuid", formatGuid.ToString()},
                                {"CID", svc.CID.ToString()},
                                {"SID", svc.SID.ToString()}
                            }
                        }
                    },
                    Data = stream,
                    MimeType = String.Empty
                };
                if(transformations != null)
                    req.Handlers.AddRange(transformations.Select(x => x.ToHandlerInfo()));
                var rsp = api.Process(req);
                return rsp.Handlers.First(x => x.Guid == IMPORT_SVC)
                    .Properties["ImportResult"].ParseAs<Int32>();
            }
        }

        /// <summary>
        /// Exports data using the specified export 
        /// operation and format.
        /// </summary>
        /// <param name="svc">The service instance.</param>
        /// <param name="name">The name of operation.</param>
        /// <param name="properties">The operation properties.</param>
        /// <param name="formatGuid">The global unique identifier of the format.</param>
        /// <param name="stream">The output data stream.</param>
        /// <param name="tform">The additional transformations.</param>
        /// <returns>The data MIME type.</returns>
        public static string ExportData(this Service svc, 
            string name, 
            IDictionary<String, String> properties, 
            Guid formatGuid,
            out Stream stream,
            IEnumerable<TransformInfo> tform = null)
        {
            if(properties == null)
                throw new ArgumentNullException("properties");
            if(String.IsNullOrWhiteSpace(name))
                throw new ArgumentException("name");

            using(var api = new ApiClient())
            {
                var req = new ApiRequest
                {
                    Infrastructure = svc.Context.Infrastructure,
                    Handlers = new List<HandlerInfo>
                    {
                        new HandlerInfo
                        {
                            Guid = EXPORT_SVC,
                            Properties = new Dictionary<String, String>(properties)
                            {
                                {"OperationName", name},
                                {"FormatGuid", formatGuid.ToString()},
                                {"CID", svc.CID.ToString()},
                                {"SID", svc.SID.ToString()}
                            }
                        }
                    },
                    Data = Stream.Null,
                    MimeType = String.Empty
                };
                if(tform != null)
                    req.Handlers.AddRange(tform.Select(x => x.ToHandlerInfo()));
                var rsp = api.Process(req);
                stream = rsp.Data;
                return rsp.MimeType;
            }
        }

        #endregion
    }
}
